﻿#include "Simulation.h"

static GLfloat cube_pos[] = {
		-1.0f,-1.0f,-1.0f,
		-1.0f,-1.0f, 1.0f,
		-1.0f, 1.0f, 1.0f,

		1.0f, 1.0f,-1.0f,
		-1.0f,-1.0f,-1.0f,
		-1.0f, 1.0f,-1.0f,

		1.0f,-1.0f, 1.0f,
		-1.0f,-1.0f,-1.0f,
		1.0f,-1.0f,-1.0f,

		1.0f, 1.0f,-1.0f,
		1.0f,-1.0f,-1.0f,
		-1.0f,-1.0f,-1.0f,

		-1.0f,-1.0f,-1.0f,
		-1.0f, 1.0f, 1.0f,
		-1.0f, 1.0f,-1.0f,

		1.0f,-1.0f, 1.0f,
		-1.0f,-1.0f, 1.0f,
		-1.0f,-1.0f,-1.0f,

		-1.0f, 1.0f, 1.0f,
		-1.0f,-1.0f, 1.0f,
		1.0f,-1.0f, 1.0f,

		1.0f, 1.0f, 1.0f,
		1.0f,-1.0f,-1.0f,
		1.0f, 1.0f,-1.0f,

		1.0f,-1.0f,-1.0f,
		1.0f, 1.0f, 1.0f,
		1.0f,-1.0f, 1.0f,

		1.0f, 1.0f, 1.0f,
		1.0f, 1.0f,-1.0f,
		-1.0f, 1.0f,-1.0f,

		1.0f, 1.0f, 1.0f,
		-1.0f, 1.0f,-1.0f,
		-1.0f, 1.0f, 1.0f,

		1.0f, 1.0f, 1.0f,
		-1.0f, 1.0f, 1.0f,
		1.0f,-1.0f, 1.0f
	};
Simulation::Simulation():cloth(NULL),transFeedFloatArr(NULL),windScale(1.0f),clothHangMode(0),cameraLocation(0),cubePos(0),windSrcMode(0)
{
	// Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
	Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 1000.0f);

	cameraXPosition = 7;
	cameraYPosition = 0;
	cameraZPosition = 30;

	View       = glm::lookAt(
	 			 glm::vec3(cameraXPosition,cameraYPosition,cameraZPosition), // Camera is at (4,3,3), in World Space
				 glm::vec3(0,0,0), // and looks at the origin
				 glm::vec3(0,1,0));  // Head is up (set to 0,-1,0 to look upside-down)
						   
	// Model matrix : an identity matrix (model will be at the origin)
	Model      = glm::mat4(1.0f);

	// Our ModelViewProjection : multiplication of our 3 matrices
	MVP        = Projection * View * Model; // Remember, matrix multiplication is the other way around

	v3GravityForce = new GLfloat[3];
	v3GravityForce[0] = 0.0f;
	v3GravityForce[1] = -0.2f;
	v3GravityForce[2] = 0.0f;

	v3WindForce = new GLfloat[3];
	v3WindForce[0] = 0.5f;
	v3WindForce[1] = 0.0f;
	v3WindForce[2] = 0.2f;

	v1Mass         = MASS;
	v1TimeStepSize = TIME_STEPSIZE;
	v1Damping      = DEFAULT_DAMPING;

	physMode = PM_CPU;
	colorMode = ECM_GLOBAL;

	bPressedB = false;
	bPressedC = false;
	bPressedD = false;
	bPressedP = false;
	bPressedV = false;
	bPressedZ = false;
	bPressedH = false;
	bPressedL = false;
	bPressedS = false;

	//cube attributes
	cube_time = 5;
	bounding_sphere_radius = 2.1;
	cube_center = vec3(0, 0, cube_time);
}

Simulation::~Simulation()
{
	delete[] v3GravityForce;
	delete[] v3WindForce;
	delete cloth;
	delete[] transFeedFloatArr;
}

void Simulation::createCloth(int num_particles_width, int num_particles_height)
{
	if (cloth)
	{
		delete cloth;
	}

	cloth = new Cloth(CLOTH_WIDTH, CLOTH_LENGTH, num_particles_width, num_particles_height, clothHangMode);
	//
	if (transFeedFloatArr) delete[] transFeedFloatArr;
	transFeedFloatArr = new float[ cloth->getNumOfTriangles() * sizeof(PostTransFeedVertexData) ];
}

bool Simulation::initWindow()
{
	// Open a window and create its OpenGL context
	window = glfwCreateWindow( 1024, 768, "COMP 6761 --- Lab1", NULL, NULL);
	if( window == NULL ){
		fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 4.3 compatible.\n" );
		glfwTerminate();
		return false;
	}
	glfwMakeContextCurrent(window);

	// Initialize GLEW
	glewExperimental = true; // Needed for core profile
	if (glewInit() != GLEW_OK) {
		fprintf(stderr, "Failed to initialize GLEW\n");
		return false;
	}

	// Ensure we can capture the escape key being pressed below
	glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

	return true;
}

void Simulation::initPhysicsProgram()
{
	// Create and compile our GLSL program from the shaders
	physicsProgramID = LoadPhysicsShaders("../Source/Shaders/PhysicsCalcs.vertexshader", 
	                                      "../Source/Shaders/PhysicsCalcs.geometryshader");

	idPhysics       = glGetAttribLocation(physicsProgramID, "id");
	positionPhysics = glGetAttribLocation(physicsProgramID, "position");
	normal          = glGetAttribLocation(physicsProgramID, "normal");
	acceleration    = glGetAttribLocation(physicsProgramID, "acceleration");
	old_position    = glGetAttribLocation(physicsProgramID, "old_position");

	Float3vfGravityForce  = glGetUniformLocation(physicsProgramID, "GravityForce");
	Float1vfMass          = glGetUniformLocation(physicsProgramID, "Mass");
	Float3vfWindDirection = glGetUniformLocation(physicsProgramID, "WindDirection");
	Float1vWindScale      = glGetUniformLocation(physicsProgramID, "WindScale");
	Float1vfTimeStepSize  = glGetUniformLocation(physicsProgramID, "TimeStepSize");
	Float1vfDamping       = glGetUniformLocation(physicsProgramID, "Damping");
}

void Simulation::initRenderingsProgram()
{
	// Create and compile our GLSL program from the shaders
	renderingProgramID = LoadShaders("../Source/Shaders/Rendering.vertexshader", 
		                             "../Source/Shaders/Rendering.fragmentshader" );

	positionRendering = glGetAttribLocation(renderingProgramID,  "position");
	color		 	  = glGetAttribLocation(renderingProgramID,  "color");	

	uniformColorID    = glGetUniformLocation(renderingProgramID, "uniform_color");
	uniformColorMode  = glGetUniformLocation(renderingProgramID, "color_mode");

	// Get a handle for our "MVP" uniform
	MatrixID = glGetUniformLocation(renderingProgramID, "MVP");
}


void Simulation::execPhysicsCalcs()
{	
	cloth->refreshVertexBuffer();
	std::vector<vertexData>& data = cloth->getVertexBuffer();

	GLuint vbo;
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, cloth->getNumOfTriangles() * sizeof(vertexData), &data[0], GL_STATIC_DRAW);

	glUseProgram(physicsProgramID);

	glEnableVertexAttribArray(idPhysics);
	glEnableVertexAttribArray(positionPhysics);
	glEnableVertexAttribArray(normal);
	glEnableVertexAttribArray(acceleration);
	glEnableVertexAttribArray(old_position);

	glVertexAttribPointer(idPhysics,       1, GL_FLOAT, GL_FALSE, sizeof(vertexData), (void*)0                                      );
	glVertexAttribPointer(positionPhysics, 3, GL_FLOAT, GL_FALSE, sizeof(vertexData), (void*)(sizeof(float)                        ));
	glVertexAttribPointer(acceleration,    3, GL_FLOAT, GL_FALSE, sizeof(vertexData), (void*)(sizeof(float) + sizeof(glm::vec3) * 3));
	glVertexAttribPointer(old_position,    3, GL_FLOAT, GL_FALSE, sizeof(vertexData), (void*)(sizeof(float) + sizeof(glm::vec3) * 4));

	glUniform3fv(Float3vfGravityForce,  1, v3GravityForce);
	glUniform3fv(Float3vfWindDirection, 1, v3WindForce);
	glUniform1fv(Float1vWindScale,      1, &windScale);
	glUniform1fv(Float1vfMass,          1, &v1Mass);
	glUniform1fv(Float1vfTimeStepSize,  1, &v1TimeStepSize);
	glUniform1fv(Float1vfDamping,       1, &v1Damping);

	// Transform Feedback Buffer 
	GLuint tbo_positions;
	glGenBuffers(1, &tbo_positions);
	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo_positions);
	glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, cloth->getNumOfTriangles()*sizeof(PostTransFeedVertexData), nullptr, GL_STATIC_READ);

	glEnable(GL_RASTERIZER_DISCARD);

	glBeginTransformFeedback(GL_POINTS);
		glDrawArrays(GL_TRIANGLES, 0, cloth->getNumOfTriangles() * sizeof(vertexData));
	glEndTransformFeedback();

	glDisable(GL_RASTERIZER_DISCARD);
	glFlush();

	glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, cloth->getNumOfTriangles() * sizeof(PostTransFeedVertexData), transFeedFloatArr);
	PostTransFeedVertexData* transFeedVertexData = (PostTransFeedVertexData*)transFeedFloatArr;

	glDisableVertexAttribArray(idPhysics);
	glDisableVertexAttribArray(positionPhysics);
	glDisableVertexAttribArray(normal);
	glDisableVertexAttribArray(acceleration);
	glDisableVertexAttribArray(old_position);

	glDeleteBuffers(1, &tbo_positions);
	glDeleteBuffers(1, &vbo);

	cloth->updateParticlesFromTransFeed(transFeedVertexData, cloth->getNumOfTriangles());
}

void Simulation::execRendering()
{
	cloth->refreshVertexBuffer();
	std::vector<vertexData>& data = cloth->getVertexBuffer();

	GLuint vbo;
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, cloth->getNumOfTriangles() * sizeof(vertexData), &data[0], GL_STATIC_DRAW);

	glUseProgram(renderingProgramID);

	glEnableVertexAttribArray(positionRendering);
	glEnableVertexAttribArray(color);

	glVertexAttribPointer(positionRendering, 3, GL_FLOAT, GL_FALSE, sizeof(vertexData), (void*)(sizeof(float)));
	glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, sizeof(vertexData), (void*)(sizeof(float) + 2*sizeof(glm::vec3)));

	// Clear the screen
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Send our transformation to the currently bound shader, 
	// in the "MVP" uniform
	glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

	GLfloat uni_color_cloth[] = {1.0f, 0.0f, 0.0f}; // red
	GLfloat uni_color_mesh[] = {1.0f, 1.0f, 0.0f}; // yellow


	switch (colorMode)
	{
		case ECM_GLOBAL:
			glUniform1i(uniformColorMode, 1);
			
			// Draw the cloth, whole in red color
			glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
			glEnable( GL_POLYGON_OFFSET_FILL );
			glPolygonOffset( 1, 1 );
			glUniform3fv(uniformColorID, 1, uni_color_cloth);
			glDrawArrays(GL_TRIANGLES, 0, data.size()); // 3 indices starting at 0 -> 1 triangle

			// Draw the mesh over the cloth 
			glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
			glUniform3fv(uniformColorID, 1, uni_color_mesh);
			glDrawArrays(GL_TRIANGLES, 0, data.size()); // 3 indices starting at 0 -> 1 triangle
			break;

		case ECM_VERTEX:
			glUniform1i(uniformColorMode, 2);
			glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
			glDrawArrays(GL_TRIANGLES, 0, data.size()); // 3 indices starting at 0 -> 1 triangle
			break;
	}

	GLuint cube_vbo;
	drawCube(cube_vbo);
	
	glDisableVertexAttribArray(positionRendering);
	glDisableVertexAttribArray(color);

	// Swap buffers
	glfwSwapBuffers(window);

	glDeleteBuffers(1, &vbo);
	glDeleteBuffers(1, &cube_vbo);
}

void Simulation::updateCubePosition()
{
	++cube_time;
	cube_center.z = cos(cube_time/55.0)*7;
}
void Simulation::drawCube(GLuint& cube_vbo)
{
	glEnable(GL_DEPTH_TEST);
	glGenBuffers(1, &cube_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, cube_vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(cube_pos), cube_pos, GL_STATIC_DRAW);
	glEnableVertexAttribArray(positionRendering);
	glVertexAttribPointer(positionRendering, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
	glEnableVertexAttribArray(color);
	glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, 0,(void*)(sizeof(float)));

	// translate by cube center
	mat4 MVP2 = glm::translate(MVP, cube_center);
	glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP2[0][0]);
	// color the cube
	GLfloat uni_color_cube[] = {0.5f,0.8f,0.4f};
	glUniform3fv(uniformColorID, 1, uni_color_cube);
	glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
	glDrawArrays(GL_TRIANGLES, 0, sizeof(cube_pos)/(3*sizeof(float)));
}

void Simulation::showInfo() 
{ 
	static double previous_seconds = glfwGetTime(); 
	static int frame_count; 
	double current_seconds = glfwGetTime(); 
	double elapsed_seconds = current_seconds - previous_seconds; 
	//
	if (elapsed_seconds > 0.25) 
	{ 
		previous_seconds = current_seconds; 
		double fps = (double) frame_count / elapsed_seconds; 
		char tmp[256]; 
		sprintf (tmp, "Cloth: %dx%d         Camera: (%d,%d,%d)         Iterations: %d         Wind Scale: %4.2f         Damping: %4.2f         fps: %4.2f         Mode: %s",
					cloth->getNumOfParticleWidth(), cloth->getNumOfParticleHeight(),
					cameraXPosition, cameraYPosition, cameraZPosition,
					cloth->getNumSpringIterations(),
					windScale,
					v1Damping,
					fps,
					physMode == PM_CPU ? "CPU" : "CPU-GPU"); 
		//
		glfwSetWindowTitle (window, tmp); 
		frame_count = 0; 
	} 
	//
	frame_count ++; 
}

void Simulation::updateMVP()
{
	View =	glm::lookAt(
	 		glm::vec3(cameraXPosition,cameraYPosition,cameraZPosition), // Camera is at (4,3,3), in World Space
			glm::vec3(0,0,0), // and looks at the origin
			glm::vec3(0,1,0));  // Head is up (set to 0,-1,0 to look upside-down)

	MVP  = Projection * View * Model; // Remember, matrix multiplication is the other way around
}

void Simulation::handleKeyboardEvents()
{
		glfwPollEvents();

		//--------------------------------
		// Physics Mode (CPU or CPU-GPU)
		//--------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_P ) == GLFW_PRESS) && (!bPressedP)) 
		{
			bPressedP = true;

			switch (physMode)
			{
				case PM_CPU:
					physMode = PM_CPU_GPU;
					break;

				case PM_CPU_GPU:
					physMode = PM_CPU;
					break;
			}

			printf("Physics: %s\n", physMode == PM_CPU ? "CPU" : "CPU-GPU");
		}
 
		if ( (glfwGetKey(window, GLFW_KEY_P ) != GLFW_PRESS) && bPressedP)
		{
			bPressedP = false;
		}

		//---------------------------
		// Color Mode 
		//---------------------------

		if ( (glfwGetKey(window, GLFW_KEY_C ) == GLFW_PRESS) && (!bPressedC)) 
		{
			bPressedC = true;

			switch (colorMode)
			{
				case ECM_GLOBAL:
					colorMode = ECM_VERTEX;
					break;

				case ECM_VERTEX:
					colorMode = ECM_GLOBAL;
					break;
			}

		}
 
		if ( (glfwGetKey(window, GLFW_KEY_C ) != GLFW_PRESS) && bPressedC)
		{
			bPressedC = false;
		}

		//----------------------------------------------
		// Increasing/decreasing number of particles 
		//----------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_V ) == GLFW_PRESS) && (!bPressedV)) 
		{
			bPressedV = true;

			// Increase or decrease ?
			int change = NUM_PARTICLES_INCR_DECR;
			if (glfwGetKey(window, GLFW_KEY_LEFT_ALT ) == GLFW_PRESS)
				change = 0-NUM_PARTICLES_INCR_DECR;

			if (cloth->getNumOfParticleWidth()+change > 0 && cloth->getNumOfParticleHeight()+change > 0)
			{
				createCloth(cloth->getNumOfParticleWidth()+change, cloth->getNumOfParticleHeight()+change);
			}
		}
 
		if ( (glfwGetKey(window, GLFW_KEY_V ) != GLFW_PRESS) && bPressedV)
		{
			bPressedV = false;
		}

		//----------------------------------------------
		// Increasing/decreasing camera's X position
		//----------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_X ) == GLFW_PRESS))
		{
			// Increase or decrease ?
			int change = CAMERA_POSITION_INCR_DECR;
			if (glfwGetKey(window, GLFW_KEY_LEFT_ALT ) == GLFW_PRESS)
				change = 0-CAMERA_POSITION_INCR_DECR;

			cameraXPosition+= change;
			updateMVP();
		}

		//----------------------------------------------
		// Increasing/decreasing camera's Y position
		//----------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_Y ) == GLFW_PRESS))
		{
			// Increase or decrease ?
			int change = CAMERA_POSITION_INCR_DECR;
			if (glfwGetKey(window, GLFW_KEY_LEFT_ALT ) == GLFW_PRESS)
				change = 0-CAMERA_POSITION_INCR_DECR;

			cameraYPosition+= change;
			updateMVP();
		}

		//----------------------------------------------
		// Increasing/decreasing camera's Z position
		//----------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_Z ) == GLFW_PRESS))
		{
			// Increase or decrease ?
			int change = CAMERA_POSITION_INCR_DECR;
			if (glfwGetKey(window, GLFW_KEY_LEFT_ALT ) == GLFW_PRESS)
				change = 0-CAMERA_POSITION_INCR_DECR;

			cameraZPosition+= change;
			updateMVP();
		}

		//----------------------------------------------------------------------------------
		// Making the cloth more/less rigid by increasing/decreasing number of iterations
		//----------------------------------------------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_R ) == GLFW_PRESS))
		{
			// Increase or decrease ?
			int change = SPRINGS_ITER_INCR_DECR;
			if (glfwGetKey(window, GLFW_KEY_LEFT_ALT ) == GLFW_PRESS)
				change = 0-SPRINGS_ITER_INCR_DECR;

			cloth->incrSpringIterations(change);
		}

		//----------------------------------------------------------------------------------
		// Incrementing/Decreasing wind speed
		//----------------------------------------------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_W ) == GLFW_PRESS))
		{
			// Increase or decrease ?
			float change = WIND_SPEED_INCR;
			if (glfwGetKey(window, GLFW_KEY_LEFT_ALT ) == GLFW_PRESS)
				change = 0-WIND_SPEED_INCR;

			windScale+= change;
			if (windScale < 0.0f) windScale= 0.0f;
		}

		//----------------------------------------------------------------------------------
		// Incrementing/Decreasing the damping
		//----------------------------------------------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_D ) == GLFW_PRESS) && !bPressedD)
		{
			bPressedD= true;

			// Increase or decrease ?
			float change = DAMPING_INCR;
			if (glfwGetKey(window, GLFW_KEY_LEFT_ALT ) == GLFW_PRESS)
				change = 0-DAMPING_INCR;

			v1Damping+= change;

			if (v1Damping > 1.0f) v1Damping= 1.0f;
			if (v1Damping < 0.0f) v1Damping= 0.0f;
		}

		if ( (glfwGetKey(window, GLFW_KEY_D ) != GLFW_PRESS) && bPressedD)
		{
			bPressedD= false;
		}

		//----------------------------------------------------------------------------------
		// Changing cloth hanging mode
		//----------------------------------------------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_H ) == GLFW_PRESS) && !bPressedH)
		{
			bPressedH= true;
			//
			clothHangMode++;
			if (clothHangMode == 4) clothHangMode = 0;
			//
			createCloth(cloth->getNumOfParticleWidth(), cloth->getNumOfParticleHeight());
		}

		if ( (glfwGetKey(window, GLFW_KEY_H ) != GLFW_PRESS) && bPressedH)
		{
			bPressedH= false;
		}

		//----------------------------------------------------------------------------------
		// Changing camera location to one of predefined locations
		//----------------------------------------------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_L ) == GLFW_PRESS) && !bPressedL)
		{
			bPressedL= true;
			//
			cameraLocation++;
			if (cameraLocation == 3) cameraLocation = 0;
			//
			switch (cameraLocation)
			{
				case 0:
					cameraXPosition= 7;
					cameraYPosition= 0;
					cameraZPosition= 30;
					break;
				case 1:
					cameraXPosition= 30;
					cameraYPosition= 0;
					cameraZPosition= 7;
					break;
				case 2:
					cameraXPosition= -6;
					cameraYPosition= 20;
					cameraZPosition= 7;
					break;
			}
			//
			updateMVP();
		}

		if ( (glfwGetKey(window, GLFW_KEY_L ) != GLFW_PRESS) && bPressedL)
		{
			bPressedL= false;
		}

		//----------------------------------------------------------------------------------
		// Changing cube's position
		//----------------------------------------------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_B ) == GLFW_PRESS) && !bPressedB)
		{
			bPressedB= true;
			//
			cubePos++;
			if (cubePos == 3) cubePos = 0;
			//
			switch (cubePos)
			{
				case 0:
					cube_center.x = -5.0f;
					break;
				case 1:
					cube_center.x = 5.0f;
					break;
				case 2:
					cube_center.x = 0.0f;
					break;
			}
		}

		if ( (glfwGetKey(window, GLFW_KEY_B ) != GLFW_PRESS) && bPressedB)
		{
			bPressedB= false;
		}

		//----------------------------------------------------------------------------------
		// Changing wind source
		//----------------------------------------------------------------------------------

		if ( (glfwGetKey(window, GLFW_KEY_S ) == GLFW_PRESS) && !bPressedS)
		{
			bPressedS= true;
			//
			windSrcMode++;
			if (windSrcMode == 3) windSrcMode = 0;
			//
			switch (windSrcMode)
			{
				case 0:
					v3WindForce[0] = 0.5f;
					v3WindForce[1] = 0.0f;
					v3WindForce[2] = 0.2f;
					break;
				case 1:
					v3WindForce[0] = -0.2f;
					v3WindForce[1] = 0.0f;
					v3WindForce[2] = 0.9f;
					break;
				case 2:
					v3WindForce[0] = 0.0f;
					v3WindForce[1] = 0.7f;
					v3WindForce[2] = 0.2f;
					break;
			}
		}

		if ( (glfwGetKey(window, GLFW_KEY_S ) != GLFW_PRESS) && bPressedS)
		{
			bPressedS= false;
		}
}

int main( void )
{
	// Initialise GLFW
	if( !glfwInit() )
	{
		fprintf( stderr, "Failed to initialize GLFW\n" );
		return -1;
	}

	glfwWindowHint(GLFW_SAMPLES, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE /*GLFW_OPENGL_CORE_PROFILE*/);

	Simulation sim;
	if (!sim.initWindow()) return -1;
	sim.createCloth(DEFAULT_NUM_PARTICLES_WIDTH, DEFAULT_NUM_PARTICLES_HEIGHT);
	sim.initPhysicsProgram();
	sim.initRenderingsProgram();

	// Dark blue background
	glClearColor(0.0f, 0.0f, 0.4f, 0.0f);

	GLuint VertexArrayID;
	glGenVertexArrays(1, &VertexArrayID);
	glBindVertexArray(VertexArrayID);

	bool bPressedP = false;
	bool bPressedC = false;
	bool bPressedV = false;
	bool bPressedZ = false;

	do{
		//-----------------------------------------------------------------
		// Showing FPS and Physic's mode info in the window's title
		//-----------------------------------------------------------------

		sim.showInfo();

		//-----------------------------------------------------------------
		// Physics calculation
		//-----------------------------------------------------------------

		switch (sim.physMode)
		{
				case PM_CPU_GPU:
					sim.execPhysicsCalcs();
					break;

				case PM_CPU:
					sim.cloth->timeStep(sim.v1Damping); // calculate the particle positions of the next frame
					sim.cloth->addForce(vec3(sim.v3GravityForce[0], sim.v3GravityForce[1], sim.v3GravityForce[2]) * TIME_STEPSIZE_POWER_2); // add gravity each frame, pointing down
					sim.cloth->windForce(vec3(sim.v3WindForce[0], sim.v3WindForce[1], sim.v3WindForce[2]) * TIME_STEPSIZE_POWER_2, sim.windScale); // generate wind each frame			
					break;
		}

		// Check Collision and update particles
		sim.cloth->checkBoundingSphereCollision(sim.cube_center, sim.bounding_sphere_radius);
		sim.updateCubePosition();

		// Check and apply springs
		sim.cloth->applyAllPrings();

		//-----------------------------------------------------------------
		// Rendering
		//-----------------------------------------------------------------

		sim.execRendering();

		//-----------------------------------------------------------------
		// Keyboard Events
		//-----------------------------------------------------------------

		sim.handleKeyboardEvents();

	} // Check if the ESC key was pressed or the window was closed
	while( glfwGetKey(sim.window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
		   glfwWindowShouldClose(sim.window) == 0 );

	// Cleanup VBO and shader
	glDeleteProgram(sim.renderingProgramID);
	glDeleteProgram(sim.physicsProgramID);
	glDeleteVertexArrays(1, &VertexArrayID);

	// Close OpenGL window and terminate GLFW
	glfwTerminate();

	return 0;
}
